package at.fhooe.mcm.logic.network.client;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import java.nio.ByteBuffer;

import at.fhooe.mcm.logic.core.Core;

import at.fhooe.mcm.logic.event.Event;
import at.fhooe.mcm.logic.event.EventListener;
import at.fhooe.mcm.logic.event.EventManager;
import at.fhooe.mcm.logic.logging.ConsoleLoggingService;
import at.fhooe.mcm.logic.logging.HTMLLoggingService;
import at.fhooe.mcm.logic.logging.XMLLoggingService;
import at.fhooe.mcm.logic.network.NetworkService;
import at.fhooe.mcm.logic.network.Peer;
import at.fhooe.mcm.logic.network.events.PeerConnectedEvent;
import at.fhooe.mcm.logic.network.events.PeerDisconnectedEvent;
import at.fhooe.mcm.logic.network.events.PeerInitializeEvent;
import at.fhooe.mcm.logic.network.events.PullEvent;
import at.fhooe.mcm.logic.sensors.events.SensorChangedEvent;
import at.fhooe.mcm.logic.sensors.events.SensorCreatedEvent;
import at.fhooe.mcm.logic.sensors.events.SensorDestroyedEvent;
import at.fhooe.mcm.logic.sensors.events.SensorUpdatedEvent;

public class Client extends Thread implements EventListener
{

	private static final String	LOGGING_SOURCE	= "Client";

	private Core				m_core;
	private String				m_host;
	private int					m_serverPort;
	
	private Peer			 	m_server;

	public Client(String host, int port, Core _core) throws IOException
	{
		m_host = host;
		m_serverPort = port;
		m_core = _core;
		
		
		EventManager.getInstance(m_core).addListener(this, PeerConnectedEvent.TYPE);
		EventManager.getInstance(m_core).addListener(this, PeerDisconnectedEvent.TYPE);
		EventManager.getInstance(m_core).addListener(this, PeerInitializeEvent.TYPE);
		
		EventManager.getInstance(m_core).addListener(this, SensorUpdatedEvent.TYPE);
		EventManager.getInstance(m_core).addListener(this, SensorCreatedEvent.TYPE);
		EventManager.getInstance(m_core).addListener(this, SensorDestroyedEvent.TYPE);
		EventManager.getInstance(m_core).addListener(this, SensorChangedEvent.TYPE);
		
		EventManager.getInstance(m_core).addListener(this, PullEvent.TYPE);
	}

	@Override
	public void run()
	{
		ConsoleLoggingService.getInstance(m_core).logInfo(LOGGING_SOURCE, "connecting to server: " + m_host + ":" + m_serverPort);
		m_server = NetworkService.getInstance(m_core).createPeer(m_host, m_serverPort);
		m_server.connect();
		
		while (true)
		{
			ByteBuffer buffer = null;

		
			while ((buffer = m_server.receive()) != null)
			{
				buffer.flip();
				byte[] byts = new byte[buffer.limit()];
				buffer.get(byts);

				deserializeEvents(byts);
				buffer.clear();
			}

			try
			{
				Thread.sleep(50);
			}
			catch (InterruptedException _ie)
			{
				ConsoleLoggingService.getInstance(m_core).logError(LOGGING_SOURCE, _ie.getMessage());
			}
		}
	}

	private void deserializeEvents(byte[] _bytes)
	{
		try
		{
			ByteArrayInputStream bos = new ByteArrayInputStream(_bytes);
			ObjectInputStream oos = new ObjectInputStream(bos);
			Object event = oos.readObject();

			if (!(event instanceof Event))
			{
				ConsoleLoggingService.getInstance(m_core).logWarning(LOGGING_SOURCE, "Transmitted object is not an event");
			}
			else
			{
				EventManager.getInstance(m_core).enqueueEvent((Event) event);
				ConsoleLoggingService.getInstance(m_core).logInfo(LOGGING_SOURCE, "Event received");
			}
		}
		catch (IOException e)
		{
			ConsoleLoggingService.getInstance(m_core).logError(LOGGING_SOURCE, e.getMessage());
		}
		catch (ClassNotFoundException e)
		{
			ConsoleLoggingService.getInstance(m_core).logError(LOGGING_SOURCE, e.getMessage());
		}
	}
	
	public void sendToPeer(Peer _p, Event _e)
	{
		ByteBuffer buffer = null;
		if ((buffer = serializeEvents(_e)) != null)
		{
			buffer.flip();
			_p.send(buffer);
		}
	}
	
	private ByteBuffer serializeEvents(Event _e)
	{
		try
		{
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(bos);

			oos.writeObject(_e);
			oos.flush();
			oos.close();
			bos.close();

			ByteBuffer buffer = ByteBuffer.allocate(bos.size());
			buffer.put(bos.toByteArray());

			return buffer;
		}
		catch (IOException ex)
		{
			ConsoleLoggingService.getInstance(m_core).logDebug(LOGGING_SOURCE, ex.getMessage());
			return null;
		}
	}

	@Override
	public void handleEvent(Event _event)
	{
		if (_event.getType().equals(PeerConnectedEvent.TYPE))
		{
			PeerConnectedEvent peerConnected = (PeerConnectedEvent) _event;
			ConsoleLoggingService.getInstance(m_core).logInfo(LOGGING_SOURCE, "Peer connected (" + peerConnected.getPeer().getAddress().toString() + ")");
		}
		else if (_event.isOfType(PeerDisconnectedEvent.TYPE))
		{
			PeerDisconnectedEvent peerDisconnected = (PeerDisconnectedEvent) _event;
			ConsoleLoggingService.getInstance(m_core).logInfo(LOGGING_SOURCE, "Peer disconnected (" + peerDisconnected.getPeer().getAddress().toString() + ")");
		}
		else if (_event.isOfType(PeerInitializeEvent.TYPE))
		{
			PeerInitializeEvent peerInitialize = (PeerInitializeEvent) _event;
			peerInitialize.getSensorData();
			
			ConsoleLoggingService.getInstance(m_core).logInfo(LOGGING_SOURCE, "Sensor list received. " + peerInitialize.getSensorData().size() + " sensors in the list.");
		}
		else if (_event.getType().equals(SensorUpdatedEvent.TYPE))
		{
			SensorUpdatedEvent sue = (SensorUpdatedEvent) _event;
			
			XMLLoggingService.getInstance(m_core).logSensorData(String.valueOf(sue.getSensorID()), sue.getSensorType(), sue.getSensorName(), String.valueOf(sue.getValue()));
			HTMLLoggingService.getInstance(m_core).logSensorData(String.valueOf(sue.getSensorID()), sue.getSensorType(), sue.getSensorName(), String.valueOf(sue.getValue()));
		}
		else if (_event.getType().equals(PullEvent.TYPE))
		{
			sendToPeer(m_server, (PullEvent)_event);
		}
	}
}
